library ieee;

use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.cpu_utils.all;
use work.alu_utils.all;

entity controlUnit is
	generic(
		register_size : integer := 32;
		Tpd : Time := unit_delay
	);
	port(
		reset_in: in bit;
		CC, V, SPV : in bit;
		opcode: in bit_vector (5 downto 0);--IR 5 downto 0
		
		halt_out:out bit;
		
		read: out bit;
		write: out bit;
		not_read: out bit;
		not_write: out bit;
		st_cond: out bit;
		
		ldIR: out bit;
		ldA: out bit;
		ldB: out bit;
		ldZ: out bit;
		ldMAR: out bit;
		ldMDR: out bit;
		
		mar_mux_s0: out bit;
		a_mux_s0: out bit;
		gpr_src_mux_s0: out bit;
		b_mux_s0: out bit;
		imm_mux_s0: out bit;
		imm_mux_s1: out bit;
		pc_gpr_mux_s0: out bit;
		mdr_mux_s0: out bit;
		mdr_mux_s1: out bit;
		
		alu_cmd: out ALU_command;
		
		Gpr_H_in: out bit;
		Gpr_L_in: out bit;
		ldIMM: out bit;
		
		push: out bit;
		pop: out bit;
		clSP: out bit;
		decSP: out bit;
		incSP: out bit;
		
		clPC: out bit;
		ldPC: out bit;
		
		clock: in bit
	);
end controlUnit;


architecture behavioral of controlUnit is
--TODO
	signal clReset, stHalt, clHalt, reset, halt: bit;
	
	signal brID, brEX, brMEM, brWB, brnotCC, bruncnd, brspv, brV: bit;
	signal cnt: integer := 0;
	
	signal alu_add: bit;
	signal 	alu_inc: bit;
	signal 	alu_transB: bit;
	signal 	alu_transA: bit;
	signal 	alu_op: bit;
	
	constant address_halt : integer := 46;
	constant address_brIF : integer := 1;
	constant address_brWB : integer := 40;
	constant address_brID_bxx_store : integer := 7;
	constant address_brID_shift : integer := 6;
	constant address_brEX : integer := 9;
	constant address_brMEM : integer := 19;
	constant address_brID_else : integer := 8;
	constant address_brEX_load_store : integer := 10;
	constant address_brEX_arth : integer := 12;
	constant address_brEX_bxx : integer := 16;
	constant address_brEX_shift_arthi : integer := 13;
	constant address_brEX_mov_push : integer := 14;
	constant address_brEX_movi : integer := 15;
	constant address_brEX_jmp_jsr : integer := 18;
	constant address_brMEM_load : integer := 20;
	constant address_brMEM_store : integer := 22;
	constant address_brMEM_rts_pop : integer := 26;
	constant address_brMEM_push : integer := 29;
	constant address_brMEM_jsr : integer := 34;
	constant address_brMEM_else : integer := 38;
	constant address_brWB_movi : integer := 41;
	constant address_brWB_pop_load : integer := 42;
	constant address_brWB_shift_arth : integer := 43;
	constant address_brWB_rts : integer := 44;
	constant address_brWB_else  : integer := 45;
	
	--signal step : state;
begin


	counter: process(clock, SPV, V, CC)
	variable brunch: bit := '0';
	begin
		if(clock'event and clock = '1')then
			--reset FF
			if(reset_in = '1') then reset<='1' after Tpd;
			else if (clReset ='1') then reset <= '0' after Tpd;
				  end if;
			end if;
			
			--haltFF
			if(stHalt = '1') then halt<='1' after Tpd;
			end if;
			if (clHalt ='1') then halt <= '0' after Tpd;
			end if;
			
			if
						(SPV = '1' and brspv = '1') 
						or (V = '1' and brV = '1')
						or (brnotCC = '1' and CC = '0') 
						or brID = '1'
						or brEX = '1'
						or brMEM = '1'
						or brWB = '1'
						or bruncnd = '1'
				then brunch := '1';
				else brunch := '0';
			end if;
			
			--if reset is active, goto T00
			if(reset = '1')then cnt<=0 after Tpd;
			else
				--if processor is not stopped change the value of cnt
				if(halt = '0')then
					if(brunch = '0')then cnt<=cnt+1 after Tpd; --inc
					else 
						-- if overflow halt the processor --ld
						if		(SPV = '1' and brspv = '1') 
								or (V = '1' and brV = '1')
							then cnt <= address_halt after Tpd;
							else --FIXME
								
								if(brnotCC = '1' and CC = '0') then cnt <= address_brWB after Tpd;
								else
									if brID = '1' 
									then
										case opcode is
										when op_halt =>
												cnt<= address_halt after Tpd;
										when op_pop | op_rts =>
												cnt<= address_brEX after Tpd;
										when op_beq | op_bnq | op_bge | op_bgt | op_ble| op_blt | op_store =>
												cnt<=address_brID_bxx_store after Tpd;
										when op_shl | op_shr | op_sar | op_ror | op_rol =>
													cnt<=address_brID_shift after Tpd;
										when	op_load | op_add | op_sub | op_not | op_or | op_xor | op_and |
												op_subi | op_addi |
												op_mov | op_movi |
												op_jmp | op_jsr |
												op_push =>
													cnt<=address_brID_else after Tpd;
										when others =>
												cnt<=address_halt after Tpd;--unknown opcode - interrupt(halt)	
										end case;
									else
										if brEX = '1' 
										then 
											case opcode is
											when op_load | op_store =>
													cnt<=address_brEX_load_store after Tpd;
											when 
												op_add| op_sub | op_not | op_or | op_xor | op_and =>
													cnt<=address_brEX_arth after Tpd;
											when 
												op_beq | op_bnq | op_bge | op_bgt | op_ble| op_blt =>
													cnt<=address_brEX_bxx after Tpd;
											when op_shl | op_shr | op_sar | op_ror | op_rol | op_subi | op_addi =>
													cnt<=address_brEX_shift_arthi after Tpd;
											when	op_mov | op_push =>
													cnt<=address_brEX_mov_push after Tpd;
											when	op_movi =>
													cnt<=address_brEX_movi after Tpd;
											when op_jmp | op_jsr =>
													cnt<=address_brEX_jmp_jsr after Tpd;
											when others =>
													cnt <= address_brMEM after Tpd;
											end case;
										else
											if brMEM = '1'
											then
												case opcode is
												when op_load  =>
														cnt<=address_brMEM_load after Tpd;
												when op_store  =>
														cnt<=address_brMEM_store after Tpd;
												when op_rts | op_pop =>
														cnt<=address_brMEM_rts_pop after Tpd;
												when op_push  =>
														cnt<=address_brMEM_push after Tpd;
												when op_jsr  =>
														cnt<=address_brMEM_jsr after Tpd;		
												when others =>
													cnt <= address_brMEM_else after Tpd;
												end case;
											else
												if brWB = '1' 
												then 
													case opcode is
													when op_movi  =>
															cnt<=address_brWB_movi after Tpd;
													when op_pop | op_load  =>
															cnt<=address_brWB_pop_load after Tpd;
													when 	op_shl | op_shr | op_sar | op_ror | op_rol | 
															op_subi | op_addi |		
															op_add| op_sub | op_not | op_or | op_xor | op_and =>
															cnt<=address_brWB_shift_arth after Tpd;
													when op_rts =>
															cnt<=address_brWB_rts after Tpd;		
													when others =>
														cnt <= address_brWB_else after Tpd;
													end case;
												else
													if bruncnd = '1' 
													then 
														case cnt is
														when 	address_brID_bxx_store
																|address_brID_shift => 
																cnt<=address_brEX after Tpd;
														when 	11
																| address_brEX_arth
																| address_brEX_bxx+1
																| address_brEX_shift_arthi
																| address_brEX_mov_push
																| address_brEX_movi => 
																cnt<=address_brMEM after Tpd;
														when 	21
																| 25
																| 28
																| 33 => 
																cnt<=address_brWB after Tpd;
														when others =>
															cnt<=address_brIF after Tpd;
														end case;
													end if;
												end if;
											end if;
										end if;
									end if;
								end if;
							
						end if;
					end if;
				end if;
			end if;
		end if;
			
			halt_out <= halt;
	end process counter;

	ctrl_unit: process(cnt)
	begin	
		if 
			cnt = 1
		then mar_mux_s0 <= '1' after Tpd;
		else mar_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 4
		then ldIR <= '1' after Tpd;
		else ldIR <= '0' after Tpd;
		end if;
		
		if 
			cnt = 1
		then alu_inc <= '1' after Tpd;
		else alu_inc <= '0' after Tpd;
		end if;
		
		if 
			cnt = 10
			or cnt = 12
			or cnt = 13
			or cnt = 14
			or cnt = 17
			or cnt = 18
		then a_mux_s0 <= '1' after Tpd;
		else a_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 27
		then pop <= '1' after Tpd;
		else pop <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
			or cnt = 7
			or cnt = 11
			or cnt = 12
			or cnt = 13
			or cnt = 14
			or cnt = 15
			or cnt = 17
			or cnt = 21
			or cnt = 25
			or cnt = 28
			or cnt = 33
			or cnt = 39
			or cnt = 41
			or cnt = 42
			or cnt = 43
			or cnt = 44
			or cnt = 45
		then bruncnd <= '1' after Tpd;
		else bruncnd <= '0' after Tpd;
		end if;
		
		if 
			cnt = 19
		then brMEM <= '1' after Tpd;
		else brMEM <= '0' after Tpd;
		end if;
		
		if 
			cnt = 10
			or cnt = 16
			or cnt = 18
		then brV <= '1' after Tpd;
		else brV <= '0' after Tpd;
		end if;
		
		if 
			cnt = 0
		then clSP <= '1' after Tpd;
		else clSP <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
			or cnt = 7
			or cnt = 8
		then ldA <= '1' after Tpd;
		else ldA <= '0' after Tpd;
		end if;
		
		if 
			cnt = 30
			or cnt = 35
		then push <= '1' after Tpd;
		else push <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
			or cnt = 7
			or cnt = 8
		then ldB <= '1' after Tpd;
		else ldB <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
		then gpr_src_mux_s0 <= '1' after Tpd;
		else gpr_src_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 15
			or cnt = 22
		then alu_transB <= '1' after Tpd;
		else alu_transB <= '0' after Tpd;
		end if;
		
		if 
			cnt = 0
		then clReset <= '1' after Tpd;
		else clReset <= '0' after Tpd;
		end if;
		
		if 
			cnt = 14
		then alu_transA <= '1' after Tpd;
		else alu_transA <= '0' after Tpd;
		end if;
		
		if 
			cnt = 2
			or cnt = 39
			or cnt = 44
		then ldPC <= '1' after Tpd;
		else ldPC <= '0' after Tpd;
		end if;
		
		if 
			cnt = 0
		then clHalt <= '1' after Tpd;
		else clHalt <= '0' after Tpd;
		end if;
		
		if 
			cnt = 1
			or cnt = 11
		then ldMAR <= '1' after Tpd;
		else ldMAR <= '0' after Tpd;
		end if;
		
		if 
			cnt = 25
		then write <= '1' after Tpd;
		else write <= '0' after Tpd;
		end if;
		
		if 
			cnt = 12
			or cnt = 13
			or cnt = 14
			or cnt = 15
			or cnt = 17
			or cnt = 18
		then st_cond <= '1' after Tpd;
		else st_cond <= '0' after Tpd;
		end if;
		
		if 
			cnt = 10
			or cnt = 13
			or cnt = 15
			or cnt = 16
			or cnt = 18
		then b_mux_s0 <= '1' after Tpd;
		else b_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 3
			or cnt = 21
		then read <= '1' after Tpd;
		else read <= '0' after Tpd;
		end if;
		
		if 
			cnt = 27
			or cnt = 32
			or cnt = 37
		then brspv <= '1' after Tpd;
		else brspv <= '0' after Tpd;
		end if;
		
		if 
			cnt = 9
		then brEX <= '1' after Tpd;
		else brEX <= '0' after Tpd;
		end if;
		
		if 
			cnt = 0
		then clPC <= '1' after Tpd;
		else clPC <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
		then imm_mux_s0 <= '1' after Tpd;
		else imm_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 7
		then imm_mux_s1 <= '1' after Tpd;
		else imm_mux_s1 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 40
		then brWB <= '1' after Tpd;
		else brWB <= '0' after Tpd;
		end if;
		
		if 
			cnt = 38
		then brnotCC <= '1' after Tpd;
		else brnotCC <= '0' after Tpd;
		end if;
		
		if 
			cnt = 42
			or cnt = 44
		then pc_gpr_mux_s0 <= '1' after Tpd;
		else pc_gpr_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 1
			or cnt = 10
			or cnt = 12
			or cnt = 13
			or cnt = 14
			or cnt = 15
			or cnt = 16
			or cnt = 18
			or cnt = 22
		then ldZ <= '1' after Tpd;
		else ldZ <= '0' after Tpd;
		end if;
		
		if 
			cnt = 3
			or cnt = 21
			or cnt = 34
		then mdr_mux_s0 <= '1' after Tpd;
		else mdr_mux_s0 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 23
			or cnt = 29
			or cnt = 34
		then mdr_mux_s1 <= '1' after Tpd;
		else mdr_mux_s1 <= '0' after Tpd;
		end if;
		
		if 
			cnt = 24
		then not_write <= '1' after Tpd;
		else not_write <= '0' after Tpd;
		end if;
		
		if 
			cnt = 12
			or cnt = 13
			or cnt = 17
		then alu_op <= '1' after Tpd;
		else alu_op <= '0' after Tpd;
		end if;
		
		if 
			cnt = 3
			or cnt = 21
			or cnt = 23
			or cnt = 28
			or cnt = 29
			or cnt = 34
		then ldMDR <= '1' after Tpd;
		else ldMDR <= '0' after Tpd;
		end if;
		
		if 
			cnt = 2
			or cnt = 20
		then not_read <= '1' after Tpd;
		else not_read <= '0' after Tpd;
		end if;
		
		if 
			cnt = 42
			or cnt = 43
		then Gpr_H_in <= '1' after Tpd;
		else Gpr_H_in <= '0' after Tpd;
		end if;
		
		if 
			cnt = 5
		then brID <= '1' after Tpd;
		else brID <= '0' after Tpd;
		end if;
		
		if 
			cnt = 41
			or cnt = 42
			or cnt = 43
		then Gpr_L_in <= '1' after Tpd;
		else Gpr_L_in <= '0' after Tpd;
		end if;
		
		if 
			cnt = 6
			or cnt = 7
			or cnt = 8
		then ldIMM <= '1' after Tpd;
		else ldIMM <= '0' after Tpd;
		end if;
		
		if 
			cnt = 46
		then stHalt <= '1' after Tpd;
		else stHalt <= '0' after Tpd;
		end if;
		
		if 
			cnt = 26
		then decSP <= '1' after Tpd;
		else decSP <= '0' after Tpd;
		end if;
		
		if 
			cnt = 31
			or cnt = 36
		then incSP <= '1' after Tpd;
		else incSP <= '0' after Tpd;
		end if;
		
		if 
			cnt = 10
			or cnt = 16
			or cnt = 18
		then alu_add <= '1' after Tpd;
		else alu_add <= '0' after Tpd;
		end if;

	end process ctrl_unit;
	
	
	
	alu:process(clock, alu_add, alu_inc, alu_transB, alu_transA, alu_op, opcode)
	begin
		if alu_add = '1' 
		then alu_cmd <= work.alu_utils.alu_add after Tpd;
		else	if alu_inc = '1' 
				then alu_cmd <= work.alu_utils.alu_inc after Tpd;
				else	if alu_transA = '1' 
						then alu_cmd <= work.alu_utils.alu_transA after Tpd;
						else	if alu_transB = '1' 
								then alu_cmd <= work.alu_utils.alu_transB after Tpd;
								else 
									if alu_op='1'
									then 	case opcode is
											when op_beq | op_bnq | op_bge | op_bgt | op_ble| op_blt | op_sub | op_subi =>
													alu_cmd <= work.alu_utils.alu_sub after Tpd;
											when op_shl=>
														alu_cmd <= work.alu_utils.alu_shl after Tpd;
											when op_shr  =>
														alu_cmd <= work.alu_utils.alu_shr after Tpd;
											when op_sar =>
														alu_cmd <= work.alu_utils.alu_sar after Tpd;
											when op_ror =>
														alu_cmd <= work.alu_utils.alu_ror after Tpd;
											when op_rol =>
														alu_cmd <= work.alu_utils.alu_rol after Tpd;
											when	op_add | op_addi =>
														alu_cmd <= work.alu_utils.alu_add after Tpd;
											when op_not =>
														alu_cmd <= work.alu_utils.alu_not after Tpd;
											when op_or =>
														alu_cmd <= work.alu_utils.alu_or after Tpd;
											when op_xor =>
														alu_cmd <= work.alu_utils.alu_xor after Tpd;
											when op_and =>
														alu_cmd <= work.alu_utils.alu_and after Tpd;
											when others =>
													alu_cmd <= work.alu_utils.alu_nop after Tpd;
											end case;
									else alu_cmd <= work.alu_utils.alu_nop after Tpd;
									end if;
								end if;
						end if;
				end if;
		end if;
	end process alu;
end behavioral;